package games

import (
	"context"
	"encoding/json"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
	"time"
	"wzgames/internal/client"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gloader"
	"wzgames/internal/model/mcomm"
	"wzgames/internal/model/statement"
	"wzgames/internal/model/user"
	"wzgames/internal/response"
	"wzgames/internal/services/dila"
	"wzgames/pkg/logger"
	"wzgames/pkg/utils/byteutil"
	"wzgames/pkg/utils/calcutil"
	"wzgames/pkg/utils/dateutil"
)

const (
	defaultTimeout15 = 15 * time.Second
	defaultTimeout10 = 10 * time.Second
	defaultTimeout5  = 5 * time.Second
)

// GenBetNo 注单ID生成
func (gt *GameTable) GenBetNo() uint64 {
	if gt.Seat.Guest {
		return client.GetID(3101, 0)
	}

	return client.GetID(3100, 0)
}

// InitBalance 初始化玩家余额
func (gt *GameTable) InitBalance() int16 {
	if gt.Seat.Guest {
		gt.Marker.SetBalance(constant.GuestMoney, 0)
		return 0
	}

	return gt.LoadBalance()
}

// LoadBalance 加载玩家余额
func (gt *GameTable) LoadBalance() int16 {
	if gt.Seat.Guest {
		return 0
	}
	ret := client.LoadBalance(&user.BalanceReq{
		Uid:        gt.Player.ID(),
		AgtId:      gt.Player.AgtID(),
		CurrId:     gt.Seat.CurrId,
		WalletType: gt.Seat.WalletType,
		BToken:     gt.Seat.BToken,
	})
	if ret.Code != 0 {
		return int16(ret.Code)
	}

	gt.Marker.SetBalance(ret.Balance, ret.Wv)

	return 0
}

func (gt *GameTable) LoadAndSendBalance() {
	if gt.Seat.Guest {
		return
	}
	code := gt.LoadBalance()
	if code == 0 {
		msg := response.AcquireMsgO13()
		xw := sfs.XWriter{}
		defer xw.Reset()
		xw.WriteUShort(uint16(2))
		xw.WriteStringS(sfs.CmdKey, constant.CmdNewBalance)
		xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
		xw.WriteShortS(sfs.KeyCode, response.CSuccess)
		xw.WriteDoubleS(constant.CmdNewBalance, gt.Balance())

		xw.WriteTo(msg)
		gt.Send2Client(msg)
	}
}

func (gt *GameTable) Balance() float64 {
	return gt.Marker.Balance()
}

func (gt *GameTable) BalanceStr() string {
	return gt.Marker.BalanceStr()
}

// CheckBetValue 检查投注额是否符合币种方案限制规则
func (gt *GameTable) CheckBetValue(betAmt string) int16 {
	if !gt.Marker.IfBalanceEnough(betAmt) {
		return response.ErrInsufficientBalance
	}
	var betMinD decimal.Decimal
	var betMaxD decimal.Decimal
	amt, _ := decimal.NewFromString(betAmt)
	if gt.GameCurr.VType == mcomm.ValueType_Float64Type {
		betMinD = decimal.NewFromFloat(gt.GameCurr.FloatValueLimit.MinBet)
		betMaxD = decimal.NewFromFloat(gt.GameCurr.FloatValueLimit.MaxBet)
	} else {
		betMinD, _ = decimal.NewFromString(gt.GameCurr.StrValueLimit.MinBet)
		betMaxD, _ = decimal.NewFromString(gt.GameCurr.StrValueLimit.MaxBet)
	}
	if amt.LessThan(betMinD) || betMaxD.IsPositive() && amt.GreaterThan(betMaxD) {
		return response.ErrAmount
	}

	return 0
}

// GenBetObj 初始化投注对象
func (gt *GameTable) GenBetObj(gAct mcomm.GameAction) *statement.PlayBet {
	return &statement.PlayBet{
		Uid:       gt.Player.ID(),
		Dateline:  dateutil.UnixMilli(),
		NodeId:    gloader.BootConfig().Node().NodeId,
		CurrId:    gt.Seat.CurrId,
		InstId:    gt.InstID(),
		GameId:    gt.Inst.GetGameId(),
		Precision: gt.Seat.Precision,
		GameAct:   gAct,
		Fee:       "0",
		GcId:      gt.Seat.GcId,
	}
}

func (gt *GameTable) UpBetObj(obj *statement.PlayBet) {
	obj.Uname = gt.Player.Name()
	obj.AgtId = gt.Player.AgtID()
	obj.Curr = gt.Seat.Currency
	obj.AgtUid = gt.Player.AUid()
	obj.WalletType = gt.Seat.WalletType
	obj.BToken = gt.Seat.BToken
}

// PlayInputData 玩家投注、取消投注等原始输入数据
func (gt *GameTable) PlayInputData(betObj *statement.PlayBet, msg *sfs.TSfsObject) {
	if gt.Seat.Guest {
		return
	}
	b, er := json.Marshal(msg)
	if er != nil {
		logger.Error("[Games] save play input data fail on json.Marshal", zap.Int64("AgtId", betObj.AgtId), zap.Uint64("UserID", betObj.Uid), zap.Int32("CurrID", betObj.CurrId), zap.Int64("InstID", gt.InstID()), zap.Error(er))
		return
	}
	betObj.Detail = byteutil.SliceByteToString(b)
	reply := &mcomm.CodeRet{}
	rpcCtx, cancelFn := context.WithTimeout(context.Background(), defaultTimeout5)
	err := client.GetUCClient().Call(rpcCtx, "PlayInput", betObj, reply)
	cancelFn()
	if err != nil {
		logger.Error("[Games] save play input data fail", zap.Int64("AgtId", betObj.AgtId), zap.Uint64("UserID", betObj.Uid), zap.Int32("CurrID", betObj.CurrId), zap.Int64("InstID", gt.InstID()), zap.Error(err))
		return
	}
}

// ImResultBet 及时结算投注
func (gt *GameTable) ImResultBet(betObj *statement.PlayBet) int16 {
	if gt.Seat.Guest {
		return gt.guestBetAndPayout(betObj.BetAmt, betObj.ImRet.WinAmt)
	}
	//dila.SendPlayState(betObj.BetId, mcomm.GameAction_GameBet, betObj.GameId, betObj.RoundId)
	betObj.ImRet.Detail = betObj.Detail
	reply := &user.BalanceRet{}
	rpcCtx, cancelFn := context.WithTimeout(context.Background(), defaultTimeout15)
	err := client.GetUCClient().Call(rpcCtx, "ImResultBet", betObj, reply)
	cancelFn()
	if err != nil {
		logger.Error("[Games] im result bet fail", zap.Int64("AgtId", betObj.AgtId), zap.Uint64("UserID", betObj.Uid), zap.Int32("CurrID", betObj.CurrId), zap.Int64("InstID", gt.InstID()), zap.Uint64("BetId", betObj.BetId), zap.Error(err))
		return response.ErrInternal
	}

	gt.Marker.SetBalance(reply.Balance, reply.Wv)
	if reply.Code == 0 { // 发往 游戏结算处理
		dila.SendPlayState(betObj.BetId, mcomm.GameAction_GameImResult, betObj.GameId, betObj.RoundId)
	}

	return int16(reply.Code)
}

// guestBetAndPayout 游客投注和派彩
func (gt *GameTable) guestBetAndPayout(betAmt, winAmt string) int16 {
	if !gt.Marker.IfBalanceEnough(betAmt) {
		return response.ErrInsufficientBalance
	}
	diff, _ := calcutil.AmountSub(winAmt, betAmt)
	return gt.Marker.BalanceInc(diff)
}

func (gt *GameTable) guestBet(betAmt string) int16 {
	if !gt.Marker.IfBalanceEnough(betAmt) {
		return response.ErrInsufficientBalance
	}
	return gt.Marker.BalanceDec(betAmt)
}

func (gt *GameTable) guestPayout(winAmt string) int16 {
	return gt.Marker.BalanceInc(winAmt)
}

// Bet 游戏投注未结算
func (gt *GameTable) Bet(betObj *statement.PlayBet) int16 {
	if gt.Seat.Guest {
		return gt.guestBet(betObj.BetAmt)
	}
	//dila.SendPlayState(betObj.BetId, betObj.GameAct, betObj.GameId, betObj.RoundId)
	reply := &user.BalanceRet{}
	rpcCtx, cancelFn := context.WithTimeout(context.Background(), defaultTimeout15)
	err := client.GetUCClient().Call(rpcCtx, "Bet", betObj, reply)
	cancelFn()
	if err != nil {
		logger.Error("[Games] bet fail", zap.Int64("AgtId", betObj.AgtId), zap.Uint64("UserID", betObj.Uid), zap.Int32("CurrID", betObj.CurrId), zap.Int64("InstID", gt.InstID()), zap.Uint64("BetId", betObj.BetId), zap.Error(err))
		return response.ErrInternal
	}
	// 更新余额
	gt.Marker.SetBalance(reply.Balance, reply.Wv)

	return int16(reply.Code)
}

// Payout 游戏投注待结算
func (gt *GameTable) Payout(betObj *statement.PlayBet) int16 {
	if gt.Seat.Guest {
		return gt.guestPayout(betObj.ImRet.WinAmt)
	}
	reply := &user.BalanceRet{}
	rpcCtx, cancelFn := context.WithTimeout(context.Background(), defaultTimeout15)
	err := client.GetUCClient().Call(rpcCtx, "Payout", betObj.ImRet, reply)
	cancelFn()
	if err != nil {
		logger.Error("[Games] bet fail", zap.Int64("AgtId", betObj.AgtId), zap.Uint64("UserID", betObj.Uid), zap.Int32("CurrID", betObj.CurrId), zap.Int64("InstID", gt.InstID()), zap.Uint64("BetId", betObj.BetId), zap.Error(err))
		return response.ErrInternal
	}

	if reply.Balance != "" {
		// 更新余额
		gt.Marker.BalanceInc(reply.Balance)
	}
	if reply.Code == 0 { // 发往 游戏结算处理
		dila.SendPlayState(betObj.BetId, betObj.ImRet.GameAct, betObj.GameId, betObj.ImRet.RoundId)
	}

	return int16(reply.Code)
}

// BetCancel 投注取消
func (gt *GameTable) BetCancel(betObj *statement.PlayBet) int16 {
	if gt.Seat.Guest {
		return gt.guestPayout(betObj.BetAmt)
	}
	reply := &user.BalanceRet{}
	rpcCtx, cancelFn := context.WithTimeout(context.Background(), defaultTimeout15)
	err := client.GetUCClient().Call(rpcCtx, "BetCancel", &statement.PlayCancel{
		BetId:    betObj.BetId,
		Dateline: dateutil.UnixMilli(),
		GameId:   betObj.GameId,
	}, reply)
	cancelFn()
	if err != nil {
		logger.Error("[Games] bet cancel fail", zap.Int64("AgtId", betObj.AgtId), zap.Uint64("UserID", betObj.Uid), zap.Int32("CurrID", betObj.CurrId), zap.Int64("InstID", gt.InstID()), zap.Uint64("BetId", betObj.BetId), zap.Error(err))
		return response.ErrInternal
	}

	if reply.Balance != "" { // 更新余额
		gt.Marker.BalanceInc(reply.Balance)
	}
	if reply.Code == 0 {
		//dila.SendPlayState(betObj.BetId, betObj.GameAct, betObj.GameId, betObj.RoundId)
	}

	return int16(reply.Code)
}

// GameUpStep 游戏每局分步数据 如 Mines、Goal、HiLo
func (gt *GameTable) GameUpStep(betObj *statement.PlayBet) int16 {
	if gt.Seat.Guest {
		return 0
	}
	reply := &mcomm.CodeRet{}
	rpcCtx, cancelFn := context.WithTimeout(context.Background(), defaultTimeout10)
	err := client.GetUCClient().Call(rpcCtx, "BetStep", &statement.PlayStep{
		BetId:    betObj.BetId,
		RoundId:  betObj.ImRet.RoundId,
		Win:      betObj.ImRet.Win,
		WinAmt:   betObj.ImRet.WinAmt,
		Payout:   betObj.ImRet.Payout,
		Dateline: betObj.ImRet.Dateline,
		Detail:   betObj.ImRet.Detail,
		GameId:   betObj.ImRet.GameId,
	}, reply)
	cancelFn()
	if err != nil {
		logger.Error("[Games] bet send to mq fail", zap.Int64("AgtId", betObj.AgtId), zap.Uint64("UserID", betObj.Uid), zap.Int32("CurrID", betObj.CurrId), zap.Int64("InstID", gt.InstID()), zap.Uint64("BetId", betObj.BetId), zap.Error(err))
		return response.ErrInternal
	}
	if reply.Code == 0 {
		//dila.SendPlayState(betObj.BetId, betObj.ImRet.GameAct, betObj.ImRet.GameId, betObj.ImRet.RoundId)
	}

	return int16(reply.Code)
}

// CheckMaxWin 检查派彩额是否超过币种方案设定的上限
func (gt *GameTable) CheckMaxWin(winAmt string) (string, float64) {
	var maxWinD decimal.Decimal
	amt, _ := decimal.NewFromString(winAmt)
	if gt.GameCurr.VType == mcomm.ValueType_Float64Type {
		maxWinD = decimal.NewFromFloat(gt.GameCurr.FloatValueLimit.MaxUserWin)
	} else {
		maxWinD, _ = decimal.NewFromString(gt.GameCurr.StrValueLimit.MaxUserWin)
	}
	if maxWinD.IsPositive() && amt.GreaterThan(maxWinD) {
		maxF, _ := maxWinD.Float64()
		return maxWinD.String(), maxF
	}
	wFloat, _ := amt.Float64()
	return winAmt, wFloat
}

// GetUserBets 玩家游戏注单
func (gt *GameTable) GetUserBets(offset, limit int64) {
	req := &statement.StmUserReq{
		Uid:       gt.Player.ID(),
		InstId:    gt.InstID(),
		Offset:    offset,
		Limit:     limit,
		CurrId:    gt.Seat.CurrId,
		Precision: gt.Seat.Precision,
		GameId:    gt.Inst.GetGameId(),
	}
	reply := &statement.StmRes{}
	rpcCtx, cancelFn := context.WithTimeout(context.Background(), 15*time.Second)
	err := client.GetBetTowerClient().Call(rpcCtx, "QueryUserBets", req, reply)
	cancelFn()
	if err != nil {
		logger.Error("[Games] get user bets fail", zap.Int64("AgtId", gt.Player.AgtID()), zap.Uint64("UserID", gt.Player.ID()), zap.Int32("CurrID", gt.Seat.CurrId), zap.Int64("InstID", gt.InstID()), zap.Error(err))
		return
	}
	if reply.Code == 0 && reply.Data != nil {
		msg := response.AcquireMsgO13()
		msg.Write(reply.Data)
		gt.Send2Client(msg)
	}
}

// GetUserBetDetail 玩家游戏注单详情
func (gt *GameTable) GetUserBetDetail(betId int64) (*statement.BetRetProtoSimple, int16) {
	req := &statement.StmBetReq{
		Uid:   gt.Player.ID(),
		BetId: uint64(betId),
	}
	reply := &statement.StmBetRes{}
	rpcCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
	err := client.GetBetTowerClient().Call(rpcCtx, "QueryUserBetDetail", req, reply)
	cancelFn()
	if err != nil {
		logger.Error("[Games] get user bet detail fail", zap.Int64("AgtId", gt.Player.AgtID()), zap.Uint64("UserID", gt.Player.ID()), zap.Int32("CurrID", gt.Seat.CurrId), zap.Int64("InstID", gt.InstID()), zap.Int64("BetId", betId), zap.Error(err))
		return nil, response.ErrInternal
	}

	return reply.Data, int16(reply.Code)
}
